<template>
  <!-- 定义一个名为v-simple-cropper的类名的外层div容器，作为整个裁剪组件的根元素 -->
  <div class="account-settings">
    <!-- 页面头部导航栏 -->
    <van-nav-bar title="账号设置" left-arrow @click="back" />
    <!-- 头像展示区域 -->
    <div class="box">
      <p class="p">头像</p>
      <div class="v-simple-cropper">
        <!-- van-uploader组件，用于实现文件上传功能，绑定了after-read事件处理函数afterRead，当文件读取完成后会触发该函数 -->
        <van-uploader :after-read="afterRead">
          <!-- 使用具名插槽（默认插槽）来定义上传组件内部的显示内容 -->
          <template #default>
            <!-- 如果imgUrl有值（即已经有图片链接），则显示图片元素，应用upload-img类名用于样式设置，通过:src绑定图片链接 -->
            <img v-if="imgUrl" class="upload-img" :src="imgUrl" alt="" />
            <!-- 否则（没有图片链接时），显示一个用于提示用户上传文件的区域，应用upload-area类名用于样式设置 -->
            <div v-else class="upload-area" />
          </template>
        </van-uploader>
        <!-- 根据showlayer的值来决定是否显示裁剪操作层，当showlayer为true时显示 -->
        <div v-show="showlayer" class="v-cropper-layer">
          <!-- van-row组件，用于创建一行布局，设置列间距（gutter）为36像素 -->
          <van-row class="layer-header" :gutter="36">
            <!-- van-col组件，占8列宽度，用于放置取消按钮 -->
            <van-col :span="8">
              <van-button
                type="danger"
                plain
                block
                size="small"
                @click="cancelHandle"
              >
                取消</van-button
              >
            </van-col>
            <!-- van-col组件，占8列宽度，用于放置旋转按钮 -->
            <van-col :span="8">
              <van-button
                type="success"
                block
                size="small"
                @click="rotateHandle"
              >
                旋转</van-button
              >
            </van-col>
            <!-- van-col组件，占8列宽度，用于放置裁剪并上传按钮 -->
            <van-col :span="8">
              <van-button
                type="primary"
                block
                size="small"
                @click="confirmHandle"
              >
                裁剪并上传</van-button
              >
            </van-col>
          </van-row>
          <!-- 用于显示待裁剪图片的img元素，通过ref属性设置引用名为cropperImg，方便在JavaScript代码中获取该元素 -->
          <img ref="cropperImg" />
        </div>
      </div>
    </div>
    <!-- 账号相关信息展示区域 -->
    <div class="top">
      <van-cell-group style="position: relative">
        <van-cell title="账号" :value="phone" />
        <van-cell
          title="昵称"
          :value="name"
          @click="router.push('/editName')"
        />
      </van-cell-group>
    </div>
    <!-- 账户相关设置链接区域 -->
    <div class="bottom">
      <van-cell-group>
        <van-cell title="账户与安全" value=">" />
        <van-cell title="隐私权政策" value=">" />
      </van-cell-group>
    </div>
    <!-- 注册事件显示按钮 -->
    <button class="btn">注册事件：2024-11-12</button>
  </div>
</template>
<script setup lang="ts">
// 引入Cropper.js库，它是用于实现图片裁剪功能的核心库
import Cropper from "cropperjs";
// 引入Cropper.js的样式文件，确保裁剪功能的样式能正确显示
import "cropperjs/dist/cropper.min.css";
// 从Vue框架中导入onMounted（在组件挂载完成后执行回调函数）、ref（用于创建响应式数据）、watch（用于监听数据变化）等函数和方法
import { onMounted, ref, watch } from "vue";
// 从vant组件库中导入一些与提示相关的函数，用于显示不同类型的提示信息，如成功、失败、加载中等提示
import { closeToast, showFailToast, showLoadingToast, showToast } from "vant";
import instance from "../axios/index";
import { useRouter } from "vue-router";
// 获取localStorage值并添加错误处理的函数
const getLocalStorageValue = (key: string): string | null => {
  try {
    const value = localStorage.getItem(key);
    return value;
  } catch (error) {
    return null;
  }
};
const router = useRouter();
// 明确各数据的类型，从localStorage获取相应数据，考虑可能为null的情况
const phone = ref<string | null>(getLocalStorageValue("phone"));
const name = ref<string | null>(getLocalStorageValue("user"));
// 点击返回上一页
const back = () => {
  router.push("/home/my");
};
// 使用defineProps定义组件接收的外部属性（props），类似于Vue 2.x中的props选项
const props = defineProps({
  // 图片的初始URL地址，类型为字符串，默认值为空字符串，如果父组件传递了该属性，则使用传递的值
  url: {
    type: String,
    default: "",
  },
  // 期望裁剪区域的宽度，类型为数字，由父组件传入具体数值来指定裁剪宽度
  width: {
    type: Number,
  },
  // 期望裁剪区域的高度，类型为数字，由父组件传入具体数值来指定裁剪高度
  height: {
    type: Number,
  },
  // 裁剪后图片的缩放比例，类型为数字，默认值为0.8，可由父组件传入具体数值来调整缩放程度
  scale: {
    type: Number,
    default: 0.8,
  },
});

// 使用defineEmits定义组件向外抛出的事件，这里定义了一个名为'update:url'的事件，用于通知父组件图片URL有更新
const emit = defineEmits(["update:url"]);

// 创建一个ref响应式数据cropperImg，初始值为null，用于存储图片元素（待裁剪图片）的DOM引用，方便后续操作该图片元素
const cropperImg = ref(null);
// 创建一个ref响应式数据cropper，初始值为null，用于存储Cropper实例，后续通过该实例调用Cropper.js提供的裁剪相关方法
const cropper = ref(null);
// 创建一个ref响应式数据showlayer，初始值为false，用于控制裁剪操作层（包含操作按钮等的层）的显示与隐藏
const showlayer = ref(false);
// 创建一个ref响应式数据imgUrl，初始值为props.url，即接收父组件传递的图片URL，后续用于显示图片以及更新裁剪后图片的URL
const imgUrl = ref(getLocalStorageValue("img"));

// 使用watch监听props.url的变化，当props.url的值发生改变时，执行回调函数
// 回调函数中判断如果新的值（val）存在，则将imgUrl的值更新为新的props.url的值，以保持显示的图片与传入的URL同步
watch(
  () => props.url,
  (val) => {
    if (val) imgUrl.value = val;
  }
);
// 初始化函数，用于创建Cropper实例并进行相关配置，在组件挂载完成后会调用该函数
function init() {
  cropper.value = new Cropper(cropperImg.value, {
    // 设置Cropper的视图模式为1，不同的视图模式决定了裁剪框与图片的交互方式以及可操作范围等，具体可参考Cropper.js文档
    viewMode: 1,
    // 根据传入的宽度和高度计算宽高比，如果width和height都有值，则按照传入的宽高计算，否则使用默认的宽高比（1 / 0.618）
    aspectRatio:
      props.width && props.height ? props.width / props.height : 1 / 0.618,
    // 设置图片在裁剪框内的拖动模式为'move'，即可以自由移动图片，还有其他拖动模式可供选择，详情参考Cropper.js文档
    dragMode: "move",
    // 设置裁剪框不可调整大小，若设置为true则用户可以手动调整裁剪框的大小，这里为false表示固定裁剪框大小
    cropBoxResizable: false,
    // 设置自动裁剪区域占整个图片可视区域的比例，这里设置为1表示自动裁剪区域覆盖整个可视区域，取值范围是0 - 1
    autoCropArea: 1,
  });
}

// 文件读取后的处理函数，由van-uploader组件的after-read事件触发，用于处理上传的文件
function afterRead(file: { file: { name: string } }) {
    // 获取上传文件的文件名中最后一个点（扩展名分隔符）的索引位置
    const tmpCnt = file.file.name.lastIndexOf('.');
    // 提取文件的扩展名（从文件名中最后一个点之后的部分）
    const exName = file.file.name.substring(tmpCnt + 1);
    // 定义一个支持的图片文件扩展名数组，用于判断上传文件的格式是否合法
    const names = ['jpg', 'jpeg', 'png'];
    // 如果上传文件的扩展名不在支持的列表中，显示一个提示信息告知用户不支持该格式，并结束函数执行，不进行后续处理
    if (!names.includes(exName)) {
        showToast('不支持的格式!');
        return;
    }

  // 获取window.URL或者window.webkitURL（用于创建对象URL，以便在浏览器中临时展示一些二进制数据，如图片文件等）
  const URL = window.URL || window.webkitURL;
  // 将上传的文件包装成Blob对象可处理的格式，这里将单个文件放入数组中（虽然只有一个文件，但Blob构造函数接收的是数组形式的数据）
  const binaryData = [file.file];
  // 使用Cropper实例的replace方法，将当前要裁剪的图片替换为新上传的图片，通过创建对象URL的方式传入新图片的二进制数据表示
  cropper.value.replace(URL.createObjectURL(new Blob(binaryData)));
  // 将showlayer的值设置为true，显示裁剪操作层，以便用户进行裁剪相关操作
  showlayer.value = true;
}

// 旋转图片的处理函数，调用Cropper实例的rotate方法，将图片顺时针旋转90度
function rotateHandle() {
  cropper.value.rotate(90);
}

// 取消裁剪操作的处理函数，调用Cropper实例的reset方法，将裁剪状态重置为初始状态（例如裁剪框位置、大小等恢复原样）
// 同时将showlayer的值设置为false，隐藏裁剪操作层，取消本次裁剪操作
function cancelHandle() {
  cropper.value.reset();
  showlayer.value = false;
}

// 确认裁剪并上传的处理函数，用于处理裁剪后的图片，将其转换为可上传的格式并模拟上传，最后更新显示的图片URL等
function confirmHandle() {
  // 获取裁剪框的数据信息，例如裁剪框在图片中的位置、宽度、高度等，这些数据可用于后续处理裁剪后的图片尺寸等操作
  const cropBox = cropper.value.getCropBoxData();
  // 获取裁剪后图片的缩放比例，如果props.scale有值则使用传入的值，否则默认为1（即不缩放）
  const scale = props.scale || 1;
  // 使用Cropper实例的getCroppedCanvas方法获取裁剪后的Canvas对象，根据传入的缩放后的宽度和高度进行配置
  // 这样可以得到裁剪后并且按照指定比例缩放后的图片的Canvas表示，后续可基于此进行更多处理，比如转换为文件格式用于上传
  cropper.value
    .getCroppedCanvas({
      width: cropBox.width * scale,
      height: cropBox.height * scale,
    })
    // 将上一步得到的Canvas对象转换为Blob对象（二进制大对象，是一种可以在网络中传输的二进制数据格式，常用于文件上传等场景）
    // 在转换完成后的回调函数中继续处理，该回调函数接收转换后的Blob对象作为参数（这里命名为blob）
 .toBlob(async (blob: BlobPart) => {
        // 获取当前的时间戳（从1970年1月1日00:00:00 UTC到当前时间的毫秒数），用于生成一个唯一的文件名，避免文件名冲突
        const timeString = new Date().getTime();
        // 创建一个新的File对象，将Blob对象包装成文件格式，指定文件名（使用时间戳命名，后缀为.jpg）以及文件类型为'image/jpeg'
        const imgFile = new File([blob], `${timeString}.jpg`, {
            type: 'image/jpeg',
        });
        // 将showlayer的值设置为false，隐藏裁剪操作层，因为裁剪和准备上传的操作已经完成
        showlayer.value = false;
        // 显示一个加载提示信息，告知用户正在上传图片，设置禁止点击（防止用户重复操作），并且持续时间设置为0，表示一直显示直到手动关闭
        showLoadingToast({
            message: '上传中...',
            duration: 0,
            forbidClick: true,
        });
        try {
            // 调用模拟的上传文件函数uploadImage，传入包装好的图片文件对象imgFile，该函数模拟了异步上传操作并返回一个Promise
            const response = await uploadImage(imgFile);
            // 关闭加载提示信息，因为上传操作（模拟的）已经完成
            closeToast();
            // 更新imgUrl的值为上传成功后返回的图片URL（在模拟上传函数中返回的包含imgUrl字段的对象中的值）
            imgUrl.value = response.imgUrl;
            // 向外抛出'update:url'事件，并传递更新后的图片URL，以便父组件可以接收到并进行相应的处理，比如更新显示的图片等
            emit('update:url', response.imgUrl);
        } catch (error) {
            // 如果上传过程中出现错误，先关闭加载提示信息
            closeToast();
            // 显示一个失败提示信息，将错误信息展示给用户，告知上传失败的原因
            showFailToast(error.message);
        }
    })
}

// 模拟文件上传的函数，返回一个Promise，用于模拟异步上传操作的过程，实际项目中需要替换为真实的与后端交互的上传逻辑
const uploadImage = async (file: string | Blob | MediaSource) => {
  //  file能拿到我上传的文件对象
  console.log('模拟上传文件：', file);
  if(file){
       // 创建一个FormData对象,用于存放上传的文件数据
    const formData = new FormData();
    // 将文件数据添加到FormData对象中，这里假设文件名为'file'，实际项目中需要根据实际情况调整文件名
    formData.append("file", file);
    // 使用axios实例发送POST请求到'/api/addimg'接口，上传图片
    instance.post("/api/addimg", formData).then((res) => {
      // 打印后端返回的图片地址信息
      // console.log(res.data.url)
      // 将响应中获取到的图片地址赋值给imgUrl响应式数据，用于更新页面显示
      imgUrl.value = res.data.url;
      const data = {
        id: getLocalStorageValue("id"),
        img: res.data.url,
      };
      // 调用修改图片接口
      instance.post("/api/updateImg", data).then((res) => {
        // console.log(res)
      });
      // 将新的图片地址保存到localStorage中，方便下次页面加载时使用
      localStorage.setItem("img", res.data.url);
    });
  }
  // 返回一个Promise对象，用于模拟异步操作的执行过程和结果处理
  return new Promise((resolve, reject) => {
    // 使用setTimeout模拟API请求延迟，这里设置延迟2秒，模拟网络请求耗时的情况
    setTimeout(() => {
      // 模拟上传成功的情况，创建一个包含imgUrl字段的对象，imgUrl的值通过URL.createObjectURL(file)生成
      // 这个URL是一个临时的、可以在浏览器端直接用于展示图片的地址，它基于传入的File对象（也就是裁剪后准备上传的图片文件）创建
      const mockResponse = { imgUrl: URL.createObjectURL(file) };
      // 调用resolve函数，表示异步操作成功完成，并将模拟的响应结果对象（包含图片URL）传递出去
      resolve(mockResponse);
      // 如果是真实的上传逻辑，当上传失败时，需要调用reject('上传失败');来表示操作失败，并传递相应的错误信息
    }, 2000);
  });
};

// 在组件挂载完成后调用init函数，进行Cropper实例的创建和相关配置操作，确保裁剪功能准备就绪
onMounted(() => {
  init();
});
</script>
<style lang="less" scoped>
.v-simple-cropper {
  text-align: center;
  padding-top: 0.1rem;
  .upload-area {
    width: 1.9rem;
    height: 1.2rem;
    /* 设置上传区域的背景图片，这里使用相对路径指向项目中的assets文件夹下的1.jpg文件，并且设置背景图片大小铺满整个区域 */
    background-size: 100% 100%;
  }
  .upload-img {
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    margin-left: 2.6rem;
  }
  /deep/.van-uploader__upload {
    margin: 0 !important;
  }
  .v-cropper-layer {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: #fff;
    z-index: 200;
    .layer-header {
      position: absolute;
      bottom: 0;
      left: 0;
      z-index: 200;
      width: 100%;
      padding: 0 0.16rem 0.12rem;
      box-sizing: border-box;
    }
    img {
      position: inherit !important;
      border-radius: inherit !important;
      float: inherit !important;
    }
  }
}
.account-settings {
  width: 100%;
  height: 6.69rem;
  background: #f5f6f7;
}

.top {
  width: 100%;
  background: #fff;
  position: relative;
  height: 0.85rem;
  /* 添加缺失的高度属性值 */
}

.bottom {
  width: 100%;
  height: 1rem;
  background: #fff;
  margin-top: 0.3rem;
}

.btn {
  width: 2rem;
  height: 0.3rem;
  margin-top: 0.3rem;
  margin-left: 0.9rem;
  background: #dddddd;
  border: none;
  border-radius: 1rem;
  color: #bdbdbd;
}

.box {
  width: 100%;
  position: relative;
  height: 0.5rem;
  background-color: #fff;

  .img {
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    position: absolute;
    right: 0.2rem;
    top: 0.05rem;
    background-color: aliceblue;
  }

  .file {
    width: 0.4rem;
    height: 0.4rem;
    position: absolute;
    right: 0.2rem;
    top: 0.1rem;
    opacity: 0;
  }

  .p {
    position: absolute;
    left: 0.15rem;
    top: 0.2rem;
    font-size: 0.14rem;
  }
}
</style>